﻿using System;
using System.Collections.Generic;
using System.Text;

namespace PCR.Logic
{
    using PCR.Base;

    /// <summary>
    /// Virus type xml database
    /// </summary>
    internal class XmlStdCurve : IStdCurveAccess
    {
        protected System.Xml.XmlDocument _doc;
        protected string _filename;
        
        /// <summary>
        /// Xml tag definition
        /// </summary>
        static readonly string StdCurves_Tag = "StdCurves";
        static readonly string StdCurve_Tag = "StdCurve";
        static readonly string ObjectID_Tag = "ObjectID";
        static readonly string Name_Tag = "Name";
        static readonly string Description_Tag = "Description";
        static readonly string SampleData_Tag = "SampleData";
        static readonly string CTVal_Tag = "CTVal";
        static readonly string CellCount_Tag = "CellCount";
        static readonly string Valid_Tag = "Valid";
        static readonly string Active_Tag = "Active";
        static readonly string ActiveID_Tag = "CurveID";

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="doc">xml document</param>
        /// <param name="filename">xml filename, used to store data to the file</param>
        public XmlStdCurve(System.Xml.XmlDocument doc,
            string filename)
        {
            System.Diagnostics.Debug.Assert(doc != null);
            System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(filename));

            this._doc = doc;
            this._filename = filename;
        }

        /// <summary>
        /// Append a object
        /// </summary>
        /// <param name="item">Object instance to append</param>
        public void Append(IStdCurve item)
        {
            System.Diagnostics.Debug.Assert(item != null);

            // First get the <StdCurves> node
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            AppendInternal(item, nodeStdCurves);
            Save();
        }

        /// <summary>
        /// Append a list of object
        /// </summary>
        /// <param name="items">Object list to append</param>
        public void Append(IList<IStdCurve> items)
        {
            System.Diagnostics.Debug.Assert(items != null);
            if (items.Count == 0)
                return;

            // First get the <StdCurves> node
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            foreach (IStdCurve item in items)
            {
                AppendInternal(item, nodeStdCurves);
            }

            Save();
        }

        /// <summary>
        /// Update the object
        /// </summary>
        /// <param name="item">Object instance to update</param>
        public void Update(IStdCurve item)
        {
            System.Diagnostics.Debug.Assert(item != null);
            UpdateByID(item.ObjectID, item);
        }

        /// <summary>
        /// Update the object by ID
        /// </summary>
        /// <param name="id">Object ID</param>
        /// <param name="item">Object instance to update</param>
        public void UpdateByID(Guid id, IStdCurve item)
        {
            // Find the specified node
            System.Xml.XmlNode patientNode = FindNodeByID(item.ObjectID);
            System.Diagnostics.Debug.Assert(patientNode != null);
            patientNode.RemoveAll();
            FillNodeWithStdCurve(item, patientNode);
            Save();
        }

        /// <summary>
        /// Delete specified object
        /// </summary>
        /// <param name="item">Object instance to delete</param>
        public void Delete(IStdCurve item)
        {
            System.Diagnostics.Debug.Assert(item != null);
            DeleteByID(item.ObjectID);
        }

        /// <summary>
        /// Delete list of objects
        /// </summary>
        /// <param name="items">Objects to delete</param>
        public void Delete(IList<IStdCurve> items)
        {
            
        }

        /// <summary>
        /// Delete object by ID
        /// </summary>
        /// <param name="id">Object ID to delete</param>
        public void DeleteByID(Guid id)
        {
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            // Find the specified node
            System.Xml.XmlNode nodeStdCurve = FindNodeByID(id);
            System.Diagnostics.Debug.Assert(nodeStdCurve != null);

            nodeStdCurves.RemoveChild(nodeStdCurve);

            Save();
        }

        /// <summary>
        /// Delete all objects
        /// </summary>
        public void DeleteAll()
        {
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            nodeStdCurves.RemoveAll();
            Save();
        }

        /// <summary>
        /// Select all the objects
        /// </summary>
        /// <returns></returns>
        public IList<IStdCurve> SelectAll()
        {
            IList<IStdCurve> stdCurves = new List<IStdCurve>();

            // First get the <StdCurves> node, 
            // and this may create a new node for it
            // it does not affect us to do the following operations
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            // Select all the <StdCurve> node list
            System.Xml.XmlNodeList nodeStdCurvesList = nodeStdCurves.SelectNodes(StdCurve_Tag);
            foreach (System.Xml.XmlNode nodeStdCurve in nodeStdCurvesList)
            {
                // Create a new instance
                IStdCurve item = CreateStdCurve();

                // Fill the instance with xml node
                if (!FillStdCurveWithNode(item, nodeStdCurve))
                {
                    // Do some error information
                    Logger.Warn("Invalid StdCurve xml node, igore it!");
                    continue;
                }

                // Add to the StdCurve list
                stdCurves.Add(item);
            }

            return stdCurves;
        }

        /// <summary>
        /// Select object by ID
        /// </summary>
        /// <param name="id">Object ID to find</param>
        /// <returns>The found object instance</returns>
        public IStdCurve SelectByID(Guid id)
        {
            // Find the xml node by ID
            System.Xml.XmlNode nodeStdCurve = FindNodeByID(id);
            if (nodeStdCurve == null)
                return null;

            // Create a new instance
            IStdCurve item = CreateStdCurve();

            // Fill the instance with xml node
            if (!FillStdCurveWithNode(item, nodeStdCurve))
            {
                // Do some error information
                Logger.Error("Invalid virus type xml node!");
                return null;
            }

            return item;
        }

        /// <summary>
        /// Search the objects by specified filter 
        /// </summary>
        /// <param name="filter">The search filter</param>
        /// <returns>The found objects</returns>
        public IList<IStdCurve> Search(ISearchFilter<IStdCurve> filter)
        {
            return null;
        }

        /// <summary>
        /// Save and load the active standard curve
        /// </summary>
        public IStdCurve Active 
        {
            get { return LoadActiveCurve(); }
            set { SaveActiveCurve(value); }
        }

        protected void AppendInternal(IStdCurve item, System.Xml.XmlNode nodeStdCurves)
        {
            System.Diagnostics.Debug.Assert(item != null);
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            // Check if it is existed, if existed just report error
            if (IsExisted(item, nodeStdCurves))
            {
                System.Diagnostics.Debug.Assert(false,
                    string.Format("Duplicated Object ID {0}, Please check it!", item.ObjectID.ToString()));

                throw new ApplicationException(string.Format(
                    "Duplicated Object ID {0}, Please check it!",
                    item.ObjectID.ToString()));
            }

            // Then append to the xml file
            // Create a new <StdCurve> node
            System.Xml.XmlElement elemStdCurve = this._doc.CreateElement(StdCurve_Tag);

            FillNodeWithStdCurve(item, elemStdCurve);

            nodeStdCurves.AppendChild(elemStdCurve);
        }

        protected bool FillNodeWithStdCurve(IStdCurve item, System.Xml.XmlNode elemStdCurve)
        {
            // Append <ObjectID> attribute
            System.Xml.XmlAttribute attr = this._doc.CreateAttribute(ObjectID_Tag);
            attr.Value = item.ObjectID.ToString();
            elemStdCurve.Attributes.Append(attr);

            // Append <Name> attribute
            attr = this._doc.CreateAttribute(Name_Tag);
            attr.Value = item.Name;
            elemStdCurve.Attributes.Append(attr);

            // Append <Description> attribute
            attr = this._doc.CreateAttribute(Description_Tag);
            attr.Value = item.Description;
            elemStdCurve.Attributes.Append(attr);

            // Create <SampleData> node and attach it to <StdCurve> node
            foreach (IStdSampleData sampleData in item.SampleDataList)
            {
                // Create <SampleData> list
                System.Xml.XmlElement elemSampleData = this._doc.CreateElement(SampleData_Tag);
                
                // Append <ObjectID> attribute
                attr = this._doc.CreateAttribute(ObjectID_Tag);
                attr.Value = sampleData.ObjectID.ToString();
                elemSampleData.Attributes.Append(attr);

                // Append <CTVal> attribute
                attr = this._doc.CreateAttribute(CTVal_Tag);
                attr.Value = sampleData.CTVal.ToString();
                elemSampleData.Attributes.Append(attr);
                
                // Append <CellCount> attribute
                attr = this._doc.CreateAttribute(CellCount_Tag);
                attr.Value = sampleData.CellCount.ToString();
                elemSampleData.Attributes.Append(attr);

                // Append <Valid> attribute
                attr = this._doc.CreateAttribute(Valid_Tag);
                attr.Value = sampleData.Valid.ToString();
                elemSampleData.Attributes.Append(attr);
                
                // Append to <StdCurve> element
                elemStdCurve.AppendChild(elemSampleData);
            }

            return true;
        }

        protected System.Xml.XmlNode GetStdCurvesNode()
        {
            System.Xml.XmlNode nodeRoot = this._doc.SelectSingleNode("PCRConfig");
            System.Diagnostics.Debug.Assert(nodeRoot != null, "PCRConfig xml tag can not be empty!");
            System.Xml.XmlNode nodeStdCurves = nodeRoot.SelectSingleNode(StdCurves_Tag);
            if (nodeStdCurves == null) // Add the node if not existed
            {
                nodeStdCurves = this._doc.CreateElement(StdCurves_Tag);
                nodeRoot.AppendChild(nodeStdCurves);
            }

            return nodeStdCurves;
        }

        protected void Save()
        {
            this._doc.Save(this._filename);
        }

        protected bool IsExisted(IStdCurve item, System.Xml.XmlNode nodeStdCurves)
        {
            System.Diagnostics.Debug.Assert(item != null);
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            return false;
        }

        protected IStdCurve CreateStdCurve()
        {
            IStdCurve stdCurve = ObjectFactory.CreateObject(ObjectType.StdCurve) as IStdCurve;
            System.Diagnostics.Debug.Assert(stdCurve != null);
            return stdCurve;
        }

        protected IStdSampleData CreateSampleData()
        {
            IStdSampleData sampleData = ObjectFactory.CreateObject(ObjectType.StdSampleData) as IStdSampleData;
            System.Diagnostics.Debug.Assert(sampleData != null);
            return sampleData;
        }

        protected bool FillStdCurveWithNode(IStdCurve item, System.Xml.XmlNode node)
        {
            System.Diagnostics.Debug.Assert(item != null);
            System.Diagnostics.Debug.Assert(node != null);

            System.Xml.XmlAttribute attr;

            // Get <ObjectID> attribute
            attr = node.Attributes[ObjectID_Tag];
            System.Diagnostics.Debug.Assert(attr != null);
            item.ObjectID = new Guid(attr.Value);

            // Get <Name> attribute
            attr = node.Attributes[Name_Tag];
            System.Diagnostics.Debug.Assert(attr != null);
            item.Name = attr.Value;

            // Get <Description> attribute
            attr = node.Attributes[Description_Tag];
            System.Diagnostics.Debug.Assert(attr != null);
            item.Description = attr.Value;
            
            // Find all the <SampleData> nodes
            System.Xml.XmlNodeList nodeSampleDataList = node.SelectNodes(SampleData_Tag);
            foreach (System.Xml.XmlNode nodeSampleData in nodeSampleDataList)
            {
                IStdSampleData sampleData = CreateSampleData();

                // Get <ObjectID> attribute
                attr = nodeSampleData.Attributes[ObjectID_Tag];
                System.Diagnostics.Debug.Assert(attr != null);
                sampleData.ObjectID = new Guid(attr.Value);

                // Get <CTVal> attribute
                attr = nodeSampleData.Attributes[CTVal_Tag];
                System.Diagnostics.Debug.Assert(attr != null);
                sampleData.CTVal = double.Parse(attr.Value);

                // Get <CellCount> attribute
                attr = nodeSampleData.Attributes[CellCount_Tag];
                System.Diagnostics.Debug.Assert(attr != null);
                sampleData.CellCount = UInt64.Parse(attr.Value);

                // Get <Valid> attribute
                attr = nodeSampleData.Attributes[Valid_Tag];
                System.Diagnostics.Debug.Assert(attr != null);
                sampleData.Valid = bool.Parse(attr.Value);
                
                // Add to list
                item.SampleDataList.Add(sampleData);
            }

            return true;
        }

        protected System.Xml.XmlNode FindNodeByID(Guid id)
        {
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);

            // Select all the <StdCurve> node list
            System.Xml.XmlNodeList nodeStdCurvesList = nodeStdCurves.SelectNodes(StdCurve_Tag);
            foreach (System.Xml.XmlNode nodeStdCurve in nodeStdCurvesList)
            {
                // Judge the ID
                System.Xml.XmlAttribute objAttr = nodeStdCurve.Attributes[ObjectID_Tag];
                System.Diagnostics.Debug.Assert(objAttr != null);

                if (objAttr.Value == id.ToString())
                {
                    return nodeStdCurve;
                }
            }

            return null;
        }

        protected bool SaveActiveCurve(IStdCurve stdcurve)
        {
            // If the instance is null, means we need to move it
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);
            
            // Get the active node
            System.Xml.XmlNode nodeActive = nodeStdCurves.SelectSingleNode(Active_Tag);
            if (stdcurve == null) // Remove it
            {
                if (nodeActive != null) // Remove this node
                {
                    nodeStdCurves.RemoveChild(nodeActive);
                }

                return true;
            }
            else
            {
                if (nodeActive == null) // Create one if not existed
                {
                    nodeActive = this._doc.CreateElement(Active_Tag);
                    nodeStdCurves.AppendChild(nodeActive);
                }
                else
                {
                    // Remove all attributes
                    nodeActive.RemoveAll();
                }
            }
                        
            // Save only the Guid as link
            System.Xml.XmlAttribute attr = this._doc.CreateAttribute(ActiveID_Tag);
            attr.Value = stdcurve.ObjectID.ToString();
            nodeActive.Attributes.Append(attr);
            Save();

            return true;
        }

        protected IStdCurve LoadActiveCurve()
        {
            System.Xml.XmlNode nodeStdCurves = GetStdCurvesNode();
            System.Diagnostics.Debug.Assert(nodeStdCurves != null);
            
            // Get the active node
            System.Xml.XmlNode nodeActive = nodeStdCurves.SelectSingleNode(Active_Tag);
            if (nodeActive == null) // Not found
                return null;
            
            // Get the <ActiveID> attribute
            System.Xml.XmlAttribute attr = nodeActive.Attributes[ActiveID_Tag];
            System.Diagnostics.Debug.Assert(attr != null);
            
            Guid activeID = new Guid(attr.Value);
            return SelectByID(activeID);
        }

        protected ILogger Logger
        {
            get
            {
                return PCRRoot.Instance.LogManager.GetLogger(typeof(XmlPatientReport));
            }
        }

    }
}
